﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD {

    /// <summary>
    /// キャッシュアレイ・クラス
    /// </summary>
    /// <remarks>
    /// リソースキャッシュのための汎用クラスです。
    /// <see cref="Object"/>と任意の型のリソース(<typeparamref name="TValue"/>のペアを保存します。
    /// <see cref="Release"/>を呼び出してガーベッジコレクトを行うと既に削除された<see cref="Object"/>とペアの
    /// ペアのリソースは削除され<see cref="ReleaseHandler"/>イベントが呼ばれます。
    /// 例えばOpenGLの場合リソースはINT型で<dic>ReleaseHandler</dic>関数の中でそれを解放します。
    /// </remarks>
    /// <typeparam name="TValue">リソースの型</typeparam>
    public class CacheArray<TValue> : IEnumerable<Cache<TValue>> where TValue : struct {


        #region Field
        Dictionary<int, Cache<TValue>> cache;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        public CacheArray () {
            this.cache = new Dictionary<int, Cache<TValue>> ();
        }
        #endregion

        #region Property
        /// <summary>
        /// <see cref="Object"/>を紐づけられたリソースに変換するインデクサー
        /// </summary>
        /// <remarks>
        /// 該当するObjectが存在しなかった場合規定値(int型なら0)が返ります
        /// </remarks>
        /// <param name="obj">ソースオブジェクト</param>
        /// <returns>紐づけられたリソース</returns>
        public TValue this[Object obj] {
            get {
                var hash = obj.GetHashCode ();
                if (!cache.ContainsKey (hash)) {
                    return default (TValue);
                }
                return cache[hash].Value;
            }
        }

        /// <summary>
        /// 現在キャッシュしているリソースの総数
        /// </summary>
        public int CachedCount {
            get { return cache.Count; }
        }

        /// <summary>
        /// 現在キャッシュしているリソースを列挙する列挙子
        /// </summary>
        /// <returns>
        /// 
        /// </returns>
        IEnumerator<Cache<TValue>> IEnumerable<Cache<TValue>>.GetEnumerator () {
            return cache.Values.GetEnumerator ();
        }

        /// <summary>
        /// 現在キャッシュしているリソースを列挙する列挙子
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
            return cache.Values.GetEnumerator ();
        }
        #endregion

        #region Method
        /// <summary>
        /// キャッシュにリソースを追加します
        /// </summary>
        /// <remarks>
        /// すでに登録されているオブジェクトを再登録はできません。
        /// 不要になったりソースは<see cref="Release"/>メソッドで一括して解放するので、
        /// 個別に解放する<dic>Remove()</dic>に相当するメソッドはありません。
        /// </remarks>
        /// <param name="obj">ソース<see cref="Object"/></param>
        /// <param name="value">リソース</param>
        public void Add (Object obj, TValue value) {
            if (obj == null) {
                throw new ArgumentNullException ("Object is null");
            }
            if (cache.ContainsKey (obj.GetHashCode ())) {
                throw new ArgumentException ("Cache has already same key object");
            }

            this.cache.Add (obj.GetHashCode (), new Cache<TValue> (obj, value));
        }

        /// <summary>
        /// キャッシュからリソースを削除します
        /// </summary>
        /// <remarks>
        /// Objectが登録されていないかnullだった場合何もしません。
        /// </remarks>
        /// <param name="obj">リソースが紐づけられているオブジェクト</param>
        /// <returns>削除したらtrue, そうでなければfalse</returns>
        public bool Remove (Object obj) {
            if (obj == null) {
                return false;
            }
            return this.cache.Remove (obj.GetHashCode ());
        }

        /// <summary>
        /// GCによって回収されたオブジェクトにひもづけられていたリソースを解放します
        /// </summary>
        /// <remarks>
        /// ガーベッジコレクトによってすでに削除された<see cref="Object"/>に紐づけられていたリソースを解放します。
        /// 具体的には<see cref="ReleaseEvents"/>イベントを呼び出した後エントリーを削除します。
        /// ユーザーは<see cref="ReleaseEvents"/>イベントに適切なハンドラー関数を登録し、
        /// この関数を定期的に呼ぶことで不要になったunmanegedリソースを解放可能です。
        /// (*1)ここでToArray()してるのは「コピー」を作成するため。
        /// IEnumerableのままだと次のforeach内部のRemove()の次のイテレーションでエラーになる。
        /// 消さないように！！
        /// </remarks>
        public void Release () {
            var invalids = cache.Where (x => !x.Value.IsValid).ToArray ();   // ...(*1)
            foreach (var dic in invalids) {
                if (ReleaseEvents != null) {
                    ReleaseEvents (dic.Value.ObjectType, dic.Value.Value);
                }
                cache.Remove (dic.Key);
            }
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        public override string ToString () {
            return string.Format ("Cache: Count={0}", CachedCount);
        }

        #endregion

        #region Event
        /// <summary>
        /// リリース・ハンドラー関数
        /// </summary>
        /// <remarks>
        /// リリース実行時にリソース解放のために呼ばれるコールバック関数の型です。
        /// 引数のTypeはキーとなったオブジェクトの型です。
        /// インスタンスはGCによって既に解放済みなので型情報のみが利用可能です。
        /// </remarks>
        /// <param name="objectType">キーオブジェクトの型</param>
        /// <param name="value">リソース</param>
        public delegate void ReleaseHandler (Type objectType, TValue value);

        /// <summary>
        /// リリース・イベント
        /// </summary>
        /// <remarks>
        /// リリース実行時にリソース解放のために呼ばれるイベントです。
        /// </remarks>
        public event ReleaseHandler ReleaseEvents;

        #endregion

    }

}
